package javapop.framework;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class ParseContext<T> {
	private Map<Integer,Map<Parser<?>,ParseResult<?>>> memos;
	private Map<Integer,Map<Parser<?>,ParseError>> fails;
	private List<Parser<?>> skipParsers;
	private T userData;
	
	public ParseContext(T userData) {
		memos = new TreeMap<Integer, Map<Parser<?>,ParseResult<?>>>();
		fails = new TreeMap<Integer, Map<Parser<?>,ParseError>>();
		this.userData = userData;
		skipParsers = new ArrayList<Parser<?>>();
	}
	
	public T getUserData() {
		return userData;
	}
	
	public void skip(Parser<?> skipParser) {
		skipParsers.add(skipParser);
	}
	
	public List<Parser<?>> getSkipParsers() {
		return skipParsers;
	}
	
	public ParseResult<?> recall(Parser<?> parser, ParseInput input) {
		Map<Parser<?>,ParseResult<?>> memo = memos.get(input.getPos());
		if(memo!=null) {
			ParseResult<?> result = memo.get(parser);
			return result;
		}
		return null;
	}
	
	public ParseError isFail(Parser<?> parser, ParseInput input) {
		Map<Parser<?>,ParseError> fail = fails.get(input.getPos());
		if(fail!=null) {
			ParseError error = fail.get(parser);
			return error;
		}
		return null;
	}
	
	public void memo(Parser<?> parser, int pos, ParseInput input,ParseResult<?> result) {
		Map<Parser<?>,ParseResult<?>> memo = memos.get(pos);
		if(memo==null) {
			memo = new HashMap<Parser<?>, ParseResult<?>>();
			memos.put(pos, memo);
		}
			
		if(memo.containsKey(parser)) {
			throw new IllegalStateException("Duplicate memo entry (please report)");			
		}
		
		memo.put(parser, result);
	}
	
	public void fail(Parser<?> parser, int pos, ParseInput input,ParseError error) {
		Map<Parser<?>,ParseError> fail = fails.get(pos);
		if(fail==null) {
			fail = new HashMap<Parser<?>, ParseError>();
			fails.put(pos, fail);
		}
			
		if(fail.containsKey(parser)) {
			throw new IllegalStateException("Duplicate fail entry (please report)");			
		}
		
		fail.put(parser, error);
	}
	
	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append("Parse context:\n");
		buf.append("Memos = [ ");
		for(Integer pos : memos.keySet()) {
			buf.append("  ");
			buf.append(pos);
			buf.append("={");
			Map<Parser<?>,ParseResult<?>> memo = memos.get(pos);
			for(Parser<?> p : memo.keySet()) {
				buf.append(p.getClass().getName());
				buf.append(" ");
			}
			buf.append("}\n");
		}
		buf.append("]\n");

		buf.append("Fails = [ ");
		for(Integer pos : fails.keySet()) {
			buf.append("  ");
			buf.append(pos);
			buf.append("={");
			Map<Parser<?>,ParseError> memo = fails.get(pos);
			/*for(Parser<?> p : memo.keySet()) {
				buf.append(p.getClass().getName());
				buf.append(" ");
			}*/
			for(ParseError p : memo.values()) {
				buf.append(" (");
				buf.append(p.getStart().getLinePos());
				buf.append(", ");
				buf.append(p.getStart().getCharPos());
				buf.append(") -> (");
				buf.append(p.getEnd().getLinePos());
				buf.append(", ");
				buf.append(p.getEnd().getCharPos());
				buf.append(") ");
			}
			buf.append("}\n");
		}
		buf.append("]");
		if(userData!=null) {
			buf.append("\nUser data = ");
			buf.append(userData);
		}
		return buf.toString();
	}

	// hakim heuristic
	public ParseError deepest() {
		ParseError res = null;
		for (Map<Parser<?>,ParseError> errs : this.fails.values()) {
			for (ParseError err : errs.values()) {
				if(res == null)
					res = err;
				else if(res.getEnd().compareTo(err.getEnd()) > 0) {
					res = err;
				}
			}
		}
		return res;
	}
}
